// 《围棋》作者版权所有。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package runtime

import (
	"runtime/internal/atomic"
	"runtime/internal/sys"
	"unsafe"
)

// 帧可用于获取调用方返回的PC值的
// 片的函数/文件/行信息。
type Frames struct {
	// 呼叫者是尚未扩展到帧的PC的一部分。
	callers []uintptr

	// 帧是尚未返回的帧片段。
	frames     []Frame
	frameStore [2]Frame
}

// Frame是帧为每个调用帧返回的信息。
type Frame struct {
	// PC是此帧中位置的程序计数器。
	// 对于调用另一个帧的帧，这将是调用指令的
	// 程序计数器。由于内联，多个帧可能具有相同的PC值，但不同的符号信息。
	PC uintptr

	// Func是此调用帧的Func值。对于非Go代码或完全内联函数，这可能是nil 
	// 。
	Func *Func

	// 函数是此调用帧的包路径限定函数名。如果非空，则该字符串唯一地
	// 标识程序中的单个函数。
	// 如果未知，则可能是空字符串。
	// 如果Func不是nil，则函数==Func.Name（）。
	Function string

	// 文件和行是此帧中
	// 位置的文件名和行号。对于非叶帧，这将是
	// 调用的位置。如果未知，它们可能分别是空字符串和
	// 零。
	File string
	Line int

	// 函数的入口点程序计数器；可能是零
	// 如果不知道。如果Func不是nil，则Entry==
	// Func.Entry（）。
	Entry uintptr

	// 函数的运行时内部视图。此字段
	// 仅为Go函数设置（funcInfo.valid（）返回true），
	// 不为C函数设置。
	funcInfo funcInfo
}

// 调用者帧获取调用者返回的PC值的一部分，
// 准备返回函数/文件/行信息。
// 在完成帧之前，不要更改切片。
func CallersFrames(callers []uintptr) *Frames {
	f := &Frames{callers: callers}
	f.frames = f.frameStore[:0]
	return f
}

// Next返回一个帧，表示PC值的切片
// 中的下一个调用帧。如果已经返回了所有调用帧，则Next 
// 返回零帧。
// 
// 更多结果表明下一次调用next是否将返回有效帧。它不一定表示此调用
// 是否返回了一个。
// 
// 有关惯用用法，请参见框架示例。
func (ci *Frames) Next() (frame Frame, more bool) {
	for len(ci.frames) < 2 {
		// 查找下一帧。
		// 我们需要寻找2帧，这样我们就知道返回“更多”结果的是什么。
		if len(ci.callers) == 0 {
			break
		}
		pc := ci.callers[0]
		ci.callers = ci.callers[1:]
		funcInfo := findfunc(pc)
		if !funcInfo.valid() {
			if cgoSymbolizer != nil {
				// 预扩展cgo帧。我们也可以以递增的方式完成这个任务，但无论如何，在这种情况下都无法避免分配。
				ci.frames = append(ci.frames, expandCgoFrames(pc)...)
			}
			continue
		}
		f := funcInfo._Func()
		entry := f.Entry()
		if pc > entry {
			// 我们在有问题的指令（调用或内联标记）之后存储指令开始的pc。
			// 这样做是出于历史原因，是为了使FuncForPC 
			// 对runtime.Callers结果中的条目正确工作。
			pc--
		}
		name := funcname(funcInfo)
		if inldata := funcdata(funcInfo, _FUNCDATA_InlTree); inldata != nil {
			inltree := (*[1 << 20]inlinedCall)(inldata)
			// 不严格，因为cgoTraceback可能添加了伪造的PC 
			// 带有有效的funcInfo但无效的PCDATA。
			ix := pcdatavalue1(funcInfo, _PCDATA_InlTreeIndex, pc, nil, false)
			if ix >= 0 {
				// 注意：条目未修改。它总是指一个真实的帧，而不是一个内联的帧。
				f = nil
				name = funcnameFromNameoff(funcInfo, inltree[ix].func_)
				// 文件/行已正确。
				// TODO:是否从InlinedCall中删除文件/行？
			}
		}
		ci.frames = append(ci.frames, Frame{
			PC:       pc,
			Func:     f,
			Function: name,
			Entry:    entry,
			funcInfo: funcInfo,
			// 注意：文件，在下面设置的行
		})
	}

	// 从帧列表中弹出一帧。剩下的留着。
	// 在通常情况下避免分配，即1或2帧。
	switch len(ci.frames) {
	case 0: // 在极少数情况下，当根本没有帧时，我们返回帧{}。
		return
	case 1:
		frame = ci.frames[0]
		ci.frames = ci.frameStore[:0]
	case 2:
		frame = ci.frames[0]
		ci.frameStore[0] = ci.frames[1]
		ci.frames = ci.frameStore[:1]
	default:
		frame = ci.frames[0]
		ci.frames = ci.frames[1:]
	}
	more = len(ci.frames) > 0
	if frame.funcInfo.valid() {
		// 在我们需要返回它之前计算文件/行，因为它可能很昂贵。这避免了为我们找到的帧计算文件/行
		// 但不返回。见第32093期。
		file, line := funcline1(frame.funcInfo, frame.PC, false)
		frame.File, frame.Line = file, int(line)
	}
	return
}

// runtime\u expandFinalInlineFrame将stk中的最终pc扩展为包括所有
// “调用者”（如果pc是内联的）。
// 
// go:linkname runtime\u expandFinalInlineFrame runtime/pprof.runtime\u expandFinalInlineFrame 
func runtime_expandFinalInlineFrame(stk []uintptr) []uintptr {
	if len(stk) == 0 {
		return stk
	}
	pc := stk[len(stk)-1]
	tracepc := pc - 1

	f := findfunc(tracepc)
	if !f.valid() {
		// 不是go函数。
		return stk
	}

	inldata := funcdata(f, _FUNCDATA_InlTree)
	if inldata == nil {
		// f中没有内联内容。
		return stk
	}

	// 将上一个函数视为正常。实际上我们还没有检查过，但是由于这台电脑包含在堆栈中，我们知道它不应该被忽略。
	lastFuncID := funcID_normal

	// 从stk中卸下pc；我们将在下面重新添加它。
	stk = stk[:len(stk)-1]

	// 请参见gentraceback中的内联扩展。
	var cache pcvalueCache
	inltree := (*[1 << 20]inlinedCall)(inldata)
	for {
		// 不严格，因为cgoTraceback可能添加了伪造的PC 
		// 带有有效的funcInfo但无效的PCDATA。
		ix := pcdatavalue1(f, _PCDATA_InlTreeIndex, tracepc, &cache, false)
		if ix < 0 {
			break
		}
		if inltree[ix].funcID == funcID_wrapper && elideWrapperCalling(lastFuncID) {
			// 忽略包装
		} else {
			stk = append(stk, pc)
		}
		lastFuncID = inltree[ix].funcID
		// 备份到“调用者”中的指令。
		tracepc = f.entry + uintptr(inltree[ix].parentPc)
		pc = tracepc + 1
	}

	// N.B.我们希望保留最后一个未内联的parentPC。
	stk = append(stk, pc)

	return stk
}

// expandCgoFrames使用cgoSymbolizer钩子为pc扩展帧信息，称为
// 非Go函数。expandCgoFrames 
// 如果pc无法扩展，则返回nil。
func expandCgoFrames(pc uintptr) []Frame {
	arg := cgoSymbolizerArg{pc: pc}
	callCgoSymbolizer(&arg)

	if arg.file == nil && arg.funcName == nil {
		// 没有来自Symboler的有用信息。
		return nil
	}

	var frames []Frame
	for {
		frames = append(frames, Frame{
			PC:       pc,
			Func:     nil,
			Function: gostring(arg.funcName),
			File:     gostring(arg.file),
			Line:     int(arg.lineno),
			Entry:    arg.entry,
			// funcInfo为零，这意味着！funcInfo.valid（）。
			// 这确保了我们使用这里给出的文件/行信息。
		})
		if arg.more == 0 {
			break
		}
		callCgoSymbolizer(&arg)
	}

	// 这台电脑没有更多的帧。告诉符号器我们完成了。
	// 我们不尝试为整个框架的使用维护一个cgoSymbolizerArg，因为当我们完成时，没有好的方法告诉
	// 符号器。
	arg.pc = 0
	callCgoSymbolizer(&arg)

	return frames
}

// 注意：Func不公开实际未报告的字段，因为我们将*Func 
// 值返回给用户，并且我们希望防止他们能够用（比如）*f=Func{}覆盖数据
// 。
// 所有在*Func上操作的代码都必须调用raw（）来获取*Func 
// 或者调用funcInfo（）来获取funcInfo。

// Func表示运行二进制文件中的Go函数。
type Func struct {
	opaque struct{} // 禁止转换的未报告字段
}

func (f *Func) raw() *_func {
	return (*_func)(unsafe.Pointer(f))
}

func (f *Func) funcInfo() funcInfo {
	fn := f.raw()
	return funcInfo{fn, findmoduledatap(fn.entry)}
}

// PCDATA和FUNCDATA表索引。
// 
// 参见funcdata.h和../cmd/internal/objabi/funcdata.go。
const (
	_PCDATA_UnsafePoint   = 0
	_PCDATA_StackMapIndex = 1
	_PCDATA_InlTreeIndex  = 2

	_FUNCDATA_ArgsPointerMaps    = 0
	_FUNCDATA_LocalsPointerMaps  = 1
	_FUNCDATA_StackObjects       = 2
	_FUNCDATA_InlTree            = 3
	_FUNCDATA_OpenCodedDeferInfo = 4
	_FUNCDATA_ArgInfo            = 5

	_ArgsSizeUnknown = -0x80000000
)

const (
	// PCDATA\u非安全点值。
	_PCDATA_UnsafePointSafe   = -1 // 异步抢占安全
	_PCDATA_UnsafePointUnsafe = -2 // 异步抢占不安全

	// /\u PCDATA\u Restart1（2）应用于
	// 中的一系列指令，如果发生异步抢占，我们应该在恢复时将PC 
	// 退回到序列的开始。
	// 我们需要两个，这样我们可以区分序列的开始/结束
	// 以防两个序列相邻。
	_PCDATA_Restart1 = -3
	_PCDATA_Restart2 = -4

	// 类似于_PCDATA_restartEntry，但如果异步
	// 被抢占，则返回函数项。
	_PCDATA_RestartAtEntry = -5
)

// FuncID标识运行时需要特别处理的特定函数
// 。
// 注意，在某些涉及插件的情况下，可能会有多个特定特殊运行时函数的副本。
// 注意：此列表必须与cmd/internal/objabi/funcid.go中的列表匹配。
type funcID uint8

const (
	funcID_normal funcID = iota // 不是一个特殊函数
	funcID_abort
	funcID_asmcgocall
	funcID_asyncPreempt
	funcID_cgocallback
	funcID_debugCallV2
	funcID_gcBgMarkWorker
	funcID_goexit
	funcID_gogo
	funcID_gopanic
	funcID_handleAsyncEvent
	funcID_jmpdefer
	funcID_mcall
	funcID_morestack
	funcID_mstart
	funcID_panicwrap
	funcID_rt0_go
	funcID_runfinq
	funcID_runtime_main
	funcID_sigpanic
	funcID_systemstack
	funcID_systemstack_switch
	funcID_wrapper // 任何自动生成的代码（哈希/均衡算法、方法包装等）
)

// FuncFlag保存函数的位。
// 此列表必须与cmd/internal/objabi/funcid.go中的列表匹配。
type funcFlag uint8

const (
	// TOPFRAME表示出现在堆栈顶部的函数。ABCFDG 
	funcFlag_TOPFRAME funcFlag = 1 << iota

	// SPWRITE表示将任意值写入SP的函数
	// （除添加或减去常量之外的任何写入）。
	// 回溯例程无法将此类更改编码到
	// pcsp表中，因此函数回溯无法安全地通过
	// SPWRITE函数展开。在SPWRITE函数处停止被视为
	// 堆栈未完全展开。在某些上下文中，这是一个致命错误。
	funcFlag_SPWRITE
)

// pcHeader保存pclntab查找使用的数据。
type pcHeader struct {
	magic          uint32  // 0xFFFFFA 
	pad1, pad2     uint8   // 0,0 
	minLC          uint8   // 最小指令大小
	ptrSize        uint8   // 以字节为单位的ptr大小
	nfunc          int     // 模块中的函数数
	nfiles         uint    // 文件选项卡中的条目数。
	funcnameOffset uintptr // 从pcHeader 
	cuOffset       uintptr // 从pcHeader 
	filetabOffset  uintptr // 到filetab变量从pcHeader 
	pctabOffset    uintptr // 到pctab变量从pcHeader 
	pclnOffset     uintptr // 从pcHeader 
}

// 映像的布局信息。它是由链接器编写的。此处的任何更改都必须是
// 匹配对cmd/internal/ld/symtab中代码的更改。转到：symtab。
// moduledata存储在静态分配的非指针内存中；
// 这里的指针对垃圾收集器都不可见。
type moduledata struct {
	pcHeader     *pcHeader
	funcnametab  []byte
	cutab        []uint32
	filetab      []byte
	pctab        []byte
	pclntable    []byte
	ftab         []functab
	findfunctab  uintptr
	minpc, maxpc uintptr

	text, etext           uintptr
	noptrdata, enoptrdata uintptr
	data, edata           uintptr
	bss, ebss             uintptr
	noptrbss, enoptrbss   uintptr
	end, gcdata, gcbss    uintptr
	types, etypes         uintptr

	textsectmap []textsect
	typelinks   []int32 // 与类型的偏移量
	itablinks   []*itab

	ptab []ptabEntry

	pluginpath string
	pkghashes  []modulehash

	modulename   string
	modulehashes []modulehash

	hasmain uint8 // 1如果模块包含主函数，0否则

	gcdatamask, gcbssmask bitvector

	typemap map[typeOff]*_type // 偏移量到以前模块中的*.\r类型

	bad bool // 模块加载失败，应忽略

	next *moduledata
}

// 模块哈希用于将新模块中的ABI或
// 包与加载的程序进行比较。
// 
// 对于模块链接所针对的每个共享库，链接器在
// moduledata.ModuleHash切片中创建一个条目，其中包含模块名称、链接时看到的abi哈希
// 以及指向运行时abi哈希的指针。这些在下面的
// ModuleDatVerify1中检查。
// 
// 对于每个加载的插件，pkghashes切片都有一个模块化的
// 新加载的包，可用于检查插件版本的
// 包与以前加载的包的任何版本。
// 这是在plugin.lastmoduleinit中完成的。
type modulehash struct {
	modulename   string
	linktimehash string
	runtimehash  *string
}

// pinnedTypemaps是moduledata对象的映射[typeOff]*\u类型。
// 
// 这些类型映射对象是在运行时在堆上分配的，但是
// 对它们的唯一直接引用是在moduledata中，由
// 链接器创建并标记为SNOPTRDATA，因此GC会忽略它。
// 
// 为确保地图未被收集，我们在此处保留第二个参考。
var pinnedTypemaps []map[typeOff]*_type

var firstmoduledata moduledata  // 链接器符号
var lastmoduledatap *moduledata // 链接器符号
var modulesSlice *[]*moduledata // 请参阅活动模块

// 活动模块返回活动模块的切片。
// 
// 一旦模块的gcdatamask和gcbssmask已组装完毕且可供GC使用，模块即处于活动状态。
// 
// 这是nosplit/nowritebarrier，因为它是由
// cgo指针检查代码调用的。
// go:nosplit 
// go:nowritebarrier 
func activeModules() []*moduledata {
	p := (*[]*moduledata)(atomic.Loadp(unsafe.Pointer(&modulesSlice)))
	if p == nil {
		return nil
	}
	return *p
}

// modulesinit从所有加载的模块中创建活动模块切片。
// 
// 当动态链接器首次加载模块时，调用.init_数组
// 函数（由cmd/link编写）调用addmoduledata，
// 将模块附加到以
// firstmoduledata开头的链接列表中。
// 
// 在Go 
// 程序的生命周期中，这种情况可能发生两次。首先，如果使用-linkshared编译，则可以在程序初始化时加载许多使用-buildmode=shared编译的模块。
// 第二，Go程序可以在运行构建的模块时加载模块
// 带-buildmode=plugin。
// 
// 加载后，调用此函数初始化
// moduledata，使其可供GC使用，并创建一个新的activeModules 
// 列表。
// 
// 一次只能有一个goroutine调用modulesinit。
func modulesinit() {
	modules := new([]*moduledata)
	for md := &firstmoduledata; md != nil; md = md.next {
		if md.bad {
			continue
		}
		*modules = append(*modules, md)
		if md.gcdatamask == (bitvector{}) {
			md.gcdatamask = progToPointerMask((*byte)(unsafe.Pointer(md.gcdata)), md.edata-md.data)
			md.gcbssmask = progToPointerMask((*byte)(unsafe.Pointer(md.gcbss)), md.ebss-md.bss)
		}
	}

	// 模块按动态加载程序加载的顺序显示在moduledata链表中，但有一个例外：
	// firstmoduledata本身包含运行时的模块。这个
	// 并不总是第一个模块（当使用-buildmode=shared时，它
	// 通常是libstd.so，第二个模块）。顺序对于
	// typelinksinit很重要，因此我们将第一个模块与
	// 包含主函数的任何模块交换。
	// 
	// 见第18729期。
	for i, md := range *modules {
		if md.hasmain != 0 {
			(*modules)[0] = md
			(*modules)[i] = &firstmoduledata
			break
		}
	}

	atomicstorep(unsafe.Pointer(&modulesSlice), unsafe.Pointer(modules))
}

type functab struct {
	entry   uintptr
	funcoff uintptr
}

// 辅助文本节的映射信息

type textsect struct {
	vaddr    uintptr // 预链接节vaddr 
	length   uintptr // 节长
	baseaddr uintptr // 重定位节地址
}

const minfunc = 16                 // 最小函数大小
const pcbucketsize = 256 * minfunc // pc->func查找表中的存储桶大小

// findfunctab是一个这些结构的数组。
// 每个bucket代表4096字节的文本段。
// 每个子bucket代表256字节的文本段。
// 若要查找给定pc的函数，请找到该pc的桶和子桶。将idx和子桶值相加可获得
// 函数索引。然后扫描从
// 索引开始的functab数组以查找目标函数。
// 此表每4096字节代码使用20字节，或约0.5%的开销。
type findfuncbucket struct {
	idx        uint32
	subbuckets [16]byte
}

func moduledataverify() {
	for datap := &firstmoduledata; datap != nil; datap = datap.next {
		moduledataverify1(datap)
	}
}

const debugPcln = false

func moduledataverify1(datap *moduledata) {
	// 检查pclntab的格式是否有效。
	hdr := datap.pcHeader
	if hdr.magic != 0xfffffffa || hdr.pad1 != 0 || hdr.pad2 != 0 || hdr.minLC != sys.PCQuantum || hdr.ptrSize != sys.PtrSize {
		print("runtime: function symbol table header:", hex(hdr.magic), hex(hdr.pad1), hex(hdr.pad2), hex(hdr.minLC), hex(hdr.ptrSize))
		if datap.pluginpath != "" {
			print(", plugin:", datap.pluginpath)
		}
		println()
		throw("invalid function symbol table\n")
	}

	// ftab是程序计数器的函数查找表。
	nftab := len(datap.ftab) - 1
	for i := 0; i < nftab; i++ {
		// 注意：ftab[nftab]。输入是合法的；它是最终函数之外的地址。
		if datap.ftab[i].entry > datap.ftab[i+1].entry {
			f1 := funcInfo{(*_func)(unsafe.Pointer(&datap.pclntable[datap.ftab[i].funcoff])), datap}
			f2 := funcInfo{(*_func)(unsafe.Pointer(&datap.pclntable[datap.ftab[i+1].funcoff])), datap}
			f2name := "end"
			if i+1 < nftab {
				f2name = funcname(f2)
			}
			print("function symbol table not sorted by program counter:", hex(datap.ftab[i].entry), funcname(f1), ">", hex(datap.ftab[i+1].entry), f2name)
			if datap.pluginpath != "" {
				print(", plugin:", datap.pluginpath)
			}
			println()
			for j := 0; j <= i; j++ {
				print("\t", hex(datap.ftab[j].entry), " ", funcname(funcInfo{(*_func)(unsafe.Pointer(&datap.pclntable[datap.ftab[j].funcoff])), datap}), "\n")
			}
			if GOOS == "aix" && isarchive {
				println("-Wl,-bnoobjreorder is mandatory on aix/ppc64 with c-archive")
			}
			throw("invalid runtime symbol table")
		}
	}

	if datap.minpc != datap.ftab[0].entry ||
		datap.maxpc != datap.ftab[nftab].entry {
		throw("minpc or maxpc invalid")
	}

	for _, modulehash := range datap.modulehashes {
		if modulehash.linktimehash != *modulehash.runtimehash {
			println("abi mismatch detected between", datap.modulename, "and", modulehash.modulename)
			throw("abi mismatch")
		}
	}
}

// FuncForPC返回一个*Func，描述包含给定程序计数器地址的函数，否则为零。
// 
// 如果pc由于内联而表示多个函数，则返回
// 描述最内层函数的*Func，但最外层函数的条目为
// 。
func FuncForPC(pc uintptr) *Func {
	f := findfunc(pc)
	if !f.valid() {
		return nil
	}
	if inldata := funcdata(f, _FUNCDATA_InlTree); inldata != nil {
		// 注意：strict=false，这样坏的PC（函数之间的PC）不会使运行时崩溃。
		// 在这种情况下，我们只报告前面的函数。见第29735期。
		// TODO:在这种情况下，也许我们不应该报告任何函数。
		// 运行时当前没有函数结束信息，唉。
		if ix := pcdatavalue1(f, _PCDATA_InlTreeIndex, pc, nil, false); ix >= 0 {
			inltree := (*[1 << 20]inlinedCall)(inldata)
			name := funcnameFromNameoff(f, inltree[ix].func_)
			file, line := funcline(f, pc)
			fi := &funcinl{
				entry: f.entry, // 实（最外层）函数的输入。
				name:  name,
				file:  file,
				line:  int(line),
			}
			return (*Func)(unsafe.Pointer(fi))
		}
	}
	return f._Func()
}

// Name返回函数的名称。
func (f *Func) Name() string {
	if f == nil {
		return ""
	}
	fn := f.raw()
	if fn.entry == 0 { // 内联版本
		fi := (*funcinl)(unsafe.Pointer(fn))
		return fi.name
	}
	return funcname(f.funcInfo())
}

// Entry返回函数的入口地址。
func (f *Func) Entry() uintptr {
	fn := f.raw()
	if fn.entry == 0 { // 内联版本
		fi := (*funcinl)(unsafe.Pointer(fn))
		return fi.entry
	}
	return fn.entry
}

// FileLine返回程序计数器pc对应的
// 源代码的文件名和行号。
// 如果pc不是f中的程序
// 计数器，则结果将不准确。
func (f *Func) FileLine(pc uintptr) (file string, line int) {
	fn := f.raw()
	if fn.entry == 0 { // 内联版本
		fi := (*funcinl)(unsafe.Pointer(fn))
		return fi.file, fi.line
	}
	// 此处Pass strict=false，因为任何人都可以调用此函数，
	// 而他们可能只是错误地认为targetpc属于f。
	file, line32 := funcline1(f.funcInfo(), pc, false)
	return file, int(line32)
}

// findmoduledatap查找PC的moduledata。
// 
// 它是不完整的，因为它是isgoexception 
// 实现的一部分。
// 
// go:nosplit 
func findmoduledatap(pc uintptr) *moduledata {
	for datap := &firstmoduledata; datap != nil; datap = datap.next {
		if datap.minpc <= pc && pc < datap.maxpc {
			return datap
		}
	}
	return nil
}

type funcInfo struct {
	*_func
	datap *moduledata
}

func (f funcInfo) valid() bool {
	return f._func != nil
}

func (f funcInfo) _Func() *Func {
	return (*Func)(unsafe.Pointer(f._func))
}

// findfunc查找PC的函数元数据。
// 
// 它是nosplit，因为它是isgoexception 
// 实现的一部分。
// 
// go:nosplit 
func findfunc(pc uintptr) funcInfo {
	datap := findmoduledatap(pc)
	if datap == nil {
		return funcInfo{}
	}
	const nsub = uintptr(len(findfuncbucket{}.subbuckets))

	x := pc - datap.minpc
	b := x / pcbucketsize
	i := x % pcbucketsize / (pcbucketsize / nsub)

	ffb := (*findfuncbucket)(add(unsafe.Pointer(datap.findfunctab), b*unsafe.Sizeof(findfuncbucket{})))
	idx := ffb.idx + uint32(ffb.subbuckets[i])

	// 如果idx超出ftab的末尾，请将其设置到表的末尾并向后搜索。
	// 如果生成多个文本节来处理大型文本节，并且链接器在它们之间插入了跳转表，则可能会出现这种情况。

	if idx >= uint32(len(datap.ftab)) {
		idx = uint32(len(datap.ftab) - 1)
	}
	if pc < datap.ftab[idx].entry {
		// 对于多个文本部分，idx可能引用
		// 高于正在搜索的pc的函数地址，因此向后搜索，直到找到匹配的地址。

		for datap.ftab[idx].entry > pc && idx > 0 {
			idx--
		}
		if idx == 0 {
			throw("findfunc: bad findfunctab entry idx")
		}
	} else {
		// 使用pc>=项进行线性搜索以查找func。
		for datap.ftab[idx+1].entry <= pc {
			idx++
		}
	}
	funcoff := datap.ftab[idx].funcoff
	if funcoff == ^uintptr(0) {
		// 对于多个文本节，外部
		// 链接器可能插入Go未知的函数。这意味着func表所涵盖的PC 
		// 范围内可能存在漏洞。无效的funcoff值表示孔。
		// 另请参见cmd/link/internal/ld/pcln.go:pclntab 
		return funcInfo{}
	}
	return funcInfo{(*_func)(unsafe.Pointer(&datap.pclntable[funcoff])), datap}
}

type pcvalueCache struct {
	entries [2][8]pcvalueCacheEnt
}

type pcvalueCacheEnt struct {
	// targetpc和off一起是此缓存项的键。
	targetpc uintptr
	off      uint32
	// val是此缓存pcvalue项的值。
	val int32
}

// pcvalueCacheKey返回pcvalueCache中用于targetpc的最外层索引。
// 计算起来一定很便宜。
// 现在，请与sys.PtrSize对齐，并减少mod条目数。
// 实际上，这似乎是相当随机和均匀分布的。
func pcvalueCacheKey(targetpc uintptr) uintptr {
	return (targetpc / sys.PtrSize) % uintptr(len(pcvalueCache{}.entries))
}

// 返回PCData值以及该值开始的PC。
// TODO:仅当缓存为零时才返回启动PC。
func pcvalue(f funcInfo, off uint32, targetpc uintptr, cache *pcvalueCache, strict bool) (int32, uintptr) {
	if off == 0 {
		return -1, 0
	}

	// 检查缓存。这加快了深层堆栈的移动，这使得
	// 趋向于反复使用相同的递归函数。
	// 
	// 此缓存足够小，完全关联性
	// 比对关联性较低的
	// 缓存进行哈希运算便宜。
	if cache != nil {
		x := pcvalueCacheKey(targetpc)
		for i := range cache.entries[x] {
			// 我们首先检查，因为我们更
			// 可能有多个条目具有
			// 同一目标的不同偏移量，而不是相反，因此我们通常会在第一个子句中失败。
			ent := &cache.entries[x][i]
			if ent.off == off && ent.targetpc == targetpc {
				return ent.val, 0
			}
		}
	}

	if !f.valid() {
		if strict && panicking == 0 {
			print("runtime: no module data for ", hex(f.entry), "\n")
			throw("no module data")
		}
		return -1, 0
	}
	datap := f.datap
	p := datap.pctab[off:]
	pc := f.entry
	prevpc := pc
	val := int32(-1)
	for {
		var ok bool
		p, ok = step(p, &pc, &val, pc == f.entry)
		if !ok {
			break
		}
		if targetpc < pc {
			// 替换缓存中的随机项。随机
			// 如果
			// 递归堆栈的周期略大于缓存，则替换可防止性能下降。
			// 将新元素放在开头，因为它是最有可能被新使用的元素。
			if cache != nil {
				x := pcvalueCacheKey(targetpc)
				e := &cache.entries[x]
				ci := fastrand() % uint32(len(cache.entries[x]))
				e[ci] = e[0]
				e[0] = pcvalueCacheEnt{
					targetpc: targetpc,
					off:      off,
					val:      val,
				}
			}

			return val, prevpc
		}
		prevpc = pc
	}

	// 如果有一个表，它应该包含所有程序计数器。
	// 如果没有，就有问题了。
	if panicking != 0 || !strict {
		return -1, 0
	}

	print("runtime: invalid pc-encoded table f=", funcname(f), " pc=", hex(pc), " targetpc=", hex(targetpc), " tab=", p, "\n")

	p = datap.pctab[off:]
	pc = f.entry
	val = -1
	for {
		var ok bool
		p, ok = step(p, &pc, &val, pc == f.entry)
		if !ok {
			break
		}
		print("\tvalue=", val, " until pc=", hex(pc), "\n")
	}

	throw("invalid runtime symbol table")
	return -1, 0
}

func cfuncname(f funcInfo) *byte {
	if !f.valid() || f.nameoff == 0 {
		return nil
	}
	return &f.datap.funcnametab[f.nameoff]
}

func funcname(f funcInfo) string {
	return gostringnocopy(cfuncname(f))
}

func funcpkgpath(f funcInfo) string {
	name := funcname(f)
	i := len(name) - 1
	for ; i > 0; i-- {
		if name[i] == '/' {
			break
		}
	}
	for ; i < len(name); i++ {
		if name[i] == '.' {
			break
		}
	}
	return name[:i]
}

func cfuncnameFromNameoff(f funcInfo, nameoff int32) *byte {
	if !f.valid() {
		return nil
	}
	return &f.datap.funcnametab[nameoff]
}

func funcnameFromNameoff(f funcInfo, nameoff int32) string {
	return gostringnocopy(cfuncnameFromNameoff(f, nameoff))
}

func funcfile(f funcInfo, fileno int32) string {
	datap := f.datap
	if !f.valid() {
		return "?"
	}
	// 确保cu索引和文件偏移量有效
	if fileoff := datap.cutab[f.cuOffset+uint32(fileno)]; fileoff != ^uint32(0) {
		return gostringnocopy(&datap.filetab[fileoff])
	}
	// pcln段已损坏。
	return "?"
}

func funcline1(f funcInfo, targetpc uintptr, strict bool) (file string, line int32) {
	datap := f.datap
	if !f.valid() {
		return "?", 0
	}
	fileno, _ := pcvalue(f, f.pcfile, targetpc, nil, strict)
	line, _ = pcvalue(f, f.pcln, targetpc, nil, strict)
	if fileno == -1 || line == -1 || int(fileno) >= len(datap.filetab) {
		// 打印（“查找”，十六进制（targetpc），“输入”，funcname（f），“获取文件=”，fileno，“line=”，lineno，“\n”）
		return "?", 0
	}
	file = funcfile(f, fileno)
	return
}

func funcline(f funcInfo, targetpc uintptr) (file string, line int32) {
	return funcline1(f, targetpc, true)
}

func funcspdelta(f funcInfo, targetpc uintptr, cache *pcvalueCache) int32 {
	x, _ := pcvalue(f, f.pcsp, targetpc, cache, true)
	if x&(sys.PtrSize-1) != 0 {
		print("invalid spdelta ", funcname(f), " ", hex(f.entry), " ", hex(targetpc), " ", hex(f.pcsp), " ", x, "\n")
	}
	return x
}

// funcMaxSPDelta返回f中任意点的最大spdelta。
func funcMaxSPDelta(f funcInfo) int32 {
	datap := f.datap
	p := datap.pctab[f.pcsp:]
	pc := f.entry
	val := int32(-1)
	max := int32(0)
	for {
		var ok bool
		p, ok = step(p, &pc, &val, pc == f.entry)
		if !ok {
			return max
		}
		if val > max {
			max = val
		}
	}
}

func pcdatastart(f funcInfo, table uint32) uint32 {
	return *(*uint32)(add(unsafe.Pointer(&f.nfuncdata), unsafe.Sizeof(f.nfuncdata)+uintptr(table)*4))
}

func pcdatavalue(f funcInfo, table uint32, targetpc uintptr, cache *pcvalueCache) int32 {
	if table >= f.npcdata {
		return -1
	}
	r, _ := pcvalue(f, pcdatastart(f, table), targetpc, cache, true)
	return r
}

func pcdatavalue1(f funcInfo, table uint32, targetpc uintptr, cache *pcvalueCache, strict bool) int32 {
	if table >= f.npcdata {
		return -1
	}
	r, _ := pcvalue(f, pcdatastart(f, table), targetpc, cache, strict)
	return r
}

// 类似于pcdatavalue，但也返回此PCData值的起始PC。
// 它不需要缓存。
func pcdatavalue2(f funcInfo, table uint32, targetpc uintptr) (int32, uintptr) {
	if table >= f.npcdata {
		return -1, 0
	}
	return pcvalue(f, pcdatastart(f, table), targetpc, nil, true)
}

func funcdata(f funcInfo, i uint8) unsafe.Pointer {
	if i < 0 || i >= f.nfuncdata {
		return nil
	}
	p := add(unsafe.Pointer(&f.nfuncdata), unsafe.Sizeof(f.nfuncdata)+uintptr(f.npcdata)*4)
	if sys.PtrSize == 8 && uintptr(p)&4 != 0 {
		if uintptr(unsafe.Pointer(f._func))&4 != 0 {
			println("runtime: misaligned func", f._func)
		}
		p = add(p, 4)
	}
	return *(*unsafe.Pointer)(add(p, uintptr(i)*sys.PtrSize))
}

// 步骤前进到下一个pc，编码表中的值对。
func step(p []byte, pc *uintptr, val *int32, first bool) (newp []byte, ok bool) {
	// 对于uvdelta和pcdelta，常见的情况（~70%）是它们是一个单字节。如果是这样，请避免调用readvarint。
	uvdelta := uint32(p[0])
	if uvdelta == 0 && !first {
		return nil, false
	}
	n := uint32(1)
	if uvdelta&0x80 != 0 {
		n, uvdelta = readvarint(p)
	}
	*val += int32(-(uvdelta & 1) ^ (uvdelta >> 1))
	p = p[n:]

	pcdelta := uint32(p[0])
	n = 1
	if pcdelta&0x80 != 0 {
		n, pcdelta = readvarint(p)
	}
	p = p[n:]
	*pc += uintptr(pcdelta * sys.PCQuantum)
	return p, true
}

// readvarint从p读取变量。
func readvarint(p []byte) (read uint32, val uint32) {
	var v, shift, n uint32
	for {
		b := p[n]
		n++
		v |= uint32(b&0x7F) << (shift & 31)
		if b&0x80 == 0 {
			break
		}
		shift += 7
	}
	return n, v
}

type stackmap struct {
	n        int32   // 位图数
	nbit     int32   // 每个位图中的位数
	bytedata [1]byte // 位图，每个位图从字节边界开始
}

// go:NOWRITESBARGER 
func stackmapdata(stkmap *stackmap, n int32) bitvector {
	// 仅当stackDebug处于启用状态时才检查此不变量。
	// 许多stackmapdata的调用者已经检查了该不变量，
	// 默认情况下禁用该不变量可以使stackmapdata内联。
	if stackDebug > 0 && (n < 0 || n >= stkmap.n) {
		throw("stackmapdata: index out of range")
	}
	return bitvector{stkmap.nbit, addb(&stkmap.bytedata[0], uintptr(n*((stkmap.nbit+7)>>3)))}
}

// inlinedCall是FUNCDATA_InlTree表中条目的编码。
type inlinedCall struct {
	parent   int16  // 内联树中父级的索引，或<0 
	funcID   funcID // 被调用函数的类型
	_        byte
	file     int32 // 内联调用的perCU文件索引。请参阅cmd/link:pcln.go 
	line     int32 // 调用位置的行号
	func_    int32 // 被调用函数的名称
	parentPc int32 // 源位置为调用位置（从入口的偏移量）的指令的位置
}
